package com.ease.framework {

import com.ease.util.Diagnostics;
import com.ease.util.MixedUtil;

import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;

import flashx.textLayout.debug.assert;

import mx.collections.IList;
import mx.utils.StringUtil;


/**
 * A service that translates or transforms the given objects to some other requested
 * type, using the registered transform functions.
 */
public class TypeTransformManager
{
   private static var _instance:TypeTransformManager;
   private var _typeTransforms:Dictionary = new Dictionary();

   /**
    * A global instance of this class. This is the instance that would typically be used
    * by an application.
    *
    * <p>This instance comes pre-populated with the array-to-dictionary transform (as
    * defined by MixedUtil.toDictionary method).
    *
    * @return
    *    An instance of InjectionManager for global use.
    *
    * @see com.ease.flexutil.MixedUtil#toDictionary()
    */
   public static function get instance():TypeTransformManager {
      if (_instance == null) {
         _instance = new TypeTransformManager();
         _instance.addTransform( Array, Dictionary, MixedUtil.toDictionary );
         _instance.addTransform( IList, Dictionary, MixedUtil.toDictionary );
      }
      return _instance;
   }

   /**
    * Adds a type transformation function to the registry of transforms.
    *
    * @param fromType
    *    The type of the object to be transformed. The type should be compatible with the
    *    input argument type of the given transform function.
    *
    * @param toType
    *    The target type for the transform. The return value of the transform function
    *    should be compatible with this type.
    *
    * @param transform
    *    The callback function that performs the transformation.
    */
   public function addTransform( fromType:Class, toType:Class, transform:Function ):void {
      if (fromType == null || toType == null || transform == null) {
         return;
      }
      var key:String = hashKey( fromType, toType );
      var transInfo:TypeTransformInfo = _typeTransforms[key];
      if (transInfo != null && transInfo.transform != transform) {
         var msg:String = StringUtil.substitute(
               "A type-transform exists for these types (new one ignored): " + key );
         Diagnostics.assert( false, msg );
         return;
      }
      _typeTransforms[key] = new TypeTransformInfo( fromType, toType, transform );
   }

   /**
    * Indicates whether this class has a type transform registered for the given types.
    */
   public function hasTransform( fromType:Class, toType:Class ):Boolean {
      if (fromType == null || toType == null) {
         return false;
      }
      var key:String = hashKey( fromType, toType );
      var transInfo:TypeTransformInfo = _typeTransforms[key];
      return transInfo != null;
   }

   /**
    * Removes the given type transformation function from the registry of transforms.
    *
    * @see #addTransform()
    */
   public function removeTransform( fromType:Class, toType:Class,
                                    transform:Function ):void {
      var key:String = hashKey( fromType, toType );
      if ( _typeTransforms[key] == transform ) {
         delete _typeTransforms[key];
      }
   }

   /**
    * Transforms the given object into the specified type using the appropriate transform
    * function from the registry. Effects an identity transform (i.e., returns the given
    * object itself) if no type-appropriate transform is found.
    *
    * @param object
    *    The object that will be type-transformed.
    *
    * @param toType
    *    The target type.
    */
   public function transform( object:Object, toType:Class ):Object {
      var fromType:Class = object.constructor;
      var key:String = hashKey( fromType, toType );
      var transformInfo:TypeTransformInfo = _typeTransforms[key];
      if (transformInfo == null || transformInfo.fromType != fromType ||
          transformInfo.toType != toType) {
         // we do the identity transform when none other is available
         return object;
      }
      var transformedObject:Object = transformInfo.transform( object );
      return transformedObject;
   }

   private static function hashKey( fromType:Class, toType:Class ):String {
      var fromTypeName:String = getQualifiedClassName( fromType );
      var toTypeName:String = getQualifiedClassName( toType );
      var key:String = StringUtil.substitute( "{0} -> {1}", fromTypeName, toTypeName );
      return key;
   }
} // class

}

class TypeTransformInfo
{
   public var fromType:Class;
   public var toType:Class;
   public var transform:Function;

   public function TypeTransformInfo( fromType:Class, toType:Class, transform:Function ) {
      this.fromType = fromType;
      this.toType = toType;
      this.transform = transform;
   }
}